#include "broadcast_sender.hpp"
#include <asio.hpp>
#include <iostream>
#include <vector>

#include "result.hpp"

class BroadcastSender::Impl
{
public:
    explicit Impl()
        : socket(io_context)
    {
    }

    cpp::Result<> init(uint16_t port, const std::string& interface_address)
    {
        try
        {
            socket.open(asio::ip::udp::v4());
            socket.set_option(asio::socket_base::broadcast(true));
            endpoint = asio::ip::udp::endpoint(asio::ip::address_v4::broadcast(), port);
            return cpp::Result<>::Ok();
        }
        catch (const std::exception& e)
        {
            // std::cout << e.what() << std::endl;
            return cpp::Result<>::Err(e.what());
        }
    }

    void release()
    {
        if (socket.is_open())
        {
            socket.close();
        }
    }

    cpp::Result<> send(const std::vector<uint8_t>& data)
    {
        asio::error_code ec;
        socket.send_to(asio::buffer(data), endpoint, 0, ec);
        if (ec)
        {
            std::cout << ec.message() << std::endl;
            return cpp::Result<>::Err(ec.message());
        }
        return cpp::Result<>::Ok();
    }

private:
    asio::io_context io_context;
    asio::ip::udp::socket socket;
    asio::ip::udp::endpoint endpoint;
};

BroadcastSender::BroadcastSender()
    : pimpl(std::make_unique<Impl>())
{
}

BroadcastSender::~BroadcastSender()
{
    pimpl->release();
}

cpp::Result<> BroadcastSender::init(uint16_t port, const std::string& interface_address) const
{
    return pimpl->init(port, interface_address);
}

void BroadcastSender::release() const
{
    pimpl->release();
}

cpp::Result<> BroadcastSender::send(const std::vector<uint8_t>& data) const
{
    return pimpl->send(data);
}
